Data-as-Flag Communication
先说结论
Data-as-Flag 最容易被误解成“不要 flag”。更准确的说法是:
它没有删除同步,而是删除了 flag 的独立因果链。
传统的单边写入通常需要建立以下顺序:
1 | 写 payload → fence / quiet / completion → 写 flag → 接收方轮询 flag → 读取 payload |
Data-as-Flag 则尝试把“数据已经可安全读取”的证明放进同一个数据面事务:
1 | 原子写入 {payload, proof} → 接收方轮询 proof → 读取同一原子单元中的 payload |
这里的 proof 可以是内嵌 flag、epoch、checksum,也可以是“某段数据已经不再等于 sentinel”这一谓词。优化目标不是少传几个字节,而是减少一次独立写、一次顺序约束或一次完成队列往返,并把完成粒度从整批消息缩小到 DataBlock、cache line,甚至单个 token。
调研得到的 NVIDIA 对应关系如下:
| 机制 | NVIDIA 对应物 | 是否属于严格的 Data-as-Flag | 核心边界 |
|---|---|---|---|
| 数据块内嵌 flag | NCCL LL、LL128、LLA2A | 是 | 依赖传输原子性或可见顺序 |
| payload 后更新 signal | NVSHMEM put-with-signal、NCCL ncclPutSignal |
否,属于绑定式通知 | signal 仍是独立对象 |
| 数据附带立即数 | RDMA Write-with-Immediate | 否,属于带外通知 | immediate 进入接收完成路径 |
| checksum 证明整批到达 | 未找到 NVIDIA NCCL/NVSHMEM 公开对应实现 | 未确认 | 链路 CRC 只证明完整性,不等于应用完成 |
| sentinel 被覆盖 | 未找到 NVIDIA 公开对应实现 | 未确认 | 必须排除碰撞并处理重置与 ABA |
完成到底指什么
讨论 flag 之前,必须先把“完成”拆开。一次通信至少有四种不同的完成:
- 源端完成:传输引擎已经不再读取源 buffer,发送方可以复用它。
- 远端到达:字节已经写入远端目标内存。
- 消费者可见:接收侧计算单元读取时,能够看到与完成标志同一代的完整 payload。
- 协议完成:credit、错误状态和循环 buffer 生命周期已经更新,可以开始下一轮。
Data-as-Flag 主要压缩的是第 2、3 项之间的通知路径。它不能自动替代源 buffer 复用、流控、超时或错误恢复。NVIDIA 在介绍 NCCL 2.24 的 optional receive completion 时也保留了这个边界:LL/LL128 可以让 GPU 根据数据中的 flag 开始处理,不必等待网络栈发出 receive completion;但网络插件的请求仍会被测试,以便清理跟踪状态。NVIDIA NCCL 2.24 技术博客
换句话说,省掉的是热路径上的“数据到了吗”,不是整个协议状态机。
UBEP 的三种证明
UBEP 论文把 Data-as-Flag 分为 TFF、DC 和 SP 三种变体。它们都依赖 CM384 Unified-Bus 对 512 B DataBlock 原子写入的保证,但把确定性、带宽和流水粒度交换到了不同位置。论文 Section 3.4 与 Figure 6
Flag 与数据同块
Token-Flag Fusion(TFF)把一个 512 B DataBlock 切成 32 B flag + 480 B payload。发送方把两者拼好后执行一次 512 B 原子写;接收方一旦观察到 flag 已更新,就能由同一原子事务推知其余 480 B 也已经可见。
它的优点是确定性强、每个 DataBlock 都可以独立进入下游流水,而且不再需要发送侧 payload → barrier → flag。代价同样直接:flag 占用 6.25% 线速容量,发送侧还要 pack,接收侧要 unpack。
数据生成批次证明
Data Checksum(DC)不在每个 DataBlock 里放 flag。发送方取各块的 32 B marker 计算累计 checksum,整批数据发完后再发送 checksum;接收方对已到达的 marker 做同样计算,匹配后把这一批判为完成。
DC 把逐块控制字节换成了一次批量证明,payload 比例更高,但接收方要等待整批 checksum,token 级流水退化为 batch 级流水。此外,checksum 方案的安全性取决于校验函数、位宽、旧数据分布和重复计算过程;若存在碰撞,就可能把未完成状态误判为完成。
数据覆盖初始哨兵
Sentinel Polling(SP)把编码与检查都移到接收端:接收 buffer 先填入特殊 sentinel,发送方原样写 payload,接收方只检查每个 DataBlock 中指定的 32 B 是否已经不等于 sentinel。
这让线上字节全部都能承载 payload,理论有效带宽最高。但它把成本转移成了三项约束:
- 每轮使用前必须重置接收 buffer;
- 被检查的数据域必须保证不会合法地产生 sentinel;
- 若真实 payload 恰好等于 sentinel,接收方会一直等待。
UBEP 为 SP 预留了普通 BF16/FP16 计算不能产生的 bit pattern,并在模型初始化时检查权重和激活不会生成该模式。论文还给出均匀分布下 32 B sentinel 偶然碰撞为 $2^{-256}$ 的估计,但也明确承认理论概率不为零,SP 仍可能因碰撞发生罕见死锁。论文第 8、11 页
NCCL 已经走过这条路
LL:用 50% 带宽买低时延
NCCL LL 的 FIFO line 把每个 32 bit 数据紧跟一个 32 bit flag。一个 16 B line 实际只携带 8 B payload,另 8 B 都是 flag:
1 | union ncclLLFifoLine { |
接收端反复读取整行,只有 flag1 和 flag2 都等于当前 step 对应的期望值,才把 data1 与 data2 拼回 64 bit payload。发送端则用同一次向量 store 写入 {data1, flag, data2, flag}。NCCL 2.31.2 device.h NCCL 2.31.2 prims_ll.h
源码注释还给出了它的正确性假设:socket 路径连续接收,或者 IB/RDMA 路径至少保证 8 B 写入原子性;flag 必须排在对应 data 后面,避免不完整接收先暴露 flag。
这与 UBEP TFF 的物理直觉完全一致,只是原子粒度更小。LL 的 payload 效率只有 50%,因此适合以固定延迟为主的小消息,而不适合追求大消息峰值带宽。
LL128:同样的 6.25% 开销
LL128 把线宽扩大到 128 B,其中 15 个 64 bit word 是数据,最后 1 个 word 承担 flag,理论 payload 效率为:
$$
\eta_{LL128}=\frac{120\ \mathrm{B}}{128\ \mathrm{B}}=93.75%.
$$
巧合但很有启发性的是,UBEP TFF 的 $480/512$ 也是 **93.75%**。两者选择了不同原子粒度,却都用 1/16 的线上容量换取细粒度完成证明。NCCL 接收端由 flagThread 检查每条 128 B line 的 flag,只有当前 warp 观察到期望 step 才继续处理;源码中的 NCCL_LL128_DATAELEMS = NCCL_LL128_LINEELEMS - 1 正对应 120 B payload + 8 B flag。NCCL 2.31.2 device.h NCCL 2.31.2 prims_ll128.h
LL128 也说明了这种技巧为什么不能只靠软件模仿。它依赖平台对 128 B 写入可见顺序的支持;NCCL 官方文档明确警告,强制在不支持的平台启用 LL128 可能造成数据损坏。NCCL NCCL_PROTO 文档
LLA2A:payload + epoch 的原子槽位
NCCL 2.31.2 源码中的 ncclLLA2ASession 是与 UBEP 最接近的 NVIDIA 实现。它面向 symmetric memory 上的低时延 All-to-All,把 8 B 数据和两个 32 bit epoch 组成一个 16 B uint4:
1 | // 发送关键路径:{data_lo, epoch, data_hi, epoch} |
这是从发送与接收模板中抽出的关键路径,省略了 peer 地址计算、类型分片、循环展开和 abort 检查;完整实现见 发送端第 39–62 行 与 接收端第 154–217 行。NCCL 仓库中的 GIN/Symmetric Memory 说明进一步把这项机制概括为:利用 NVLink 16 B 原子 store,一次操作同时交付数据并发布完成。NCCL LLA2A 说明
LLA2A 没有让任意 payload 值直接决定 ready,而是保留了两个 epoch。epoch 回答“这是不是当前轮的数据”,双 buffer 与跨 session 的 epoch 跳变则避免旧槽位被下一轮误认。它仍然付出 50% 线上容量,却换来了无碰撞的完成谓词和逐槽消费能力。
这些 NCCL 公开实现呈现出共同取舍:只要延迟收益足够大,宁愿花固定元数据字节,也不把正确性押在 payload 的统计性质上。
相邻机制为何不等价
Put-with-Signal 绑定操作但不合并对象
NVSHMEM 提供 nvshmem_putmem_signal:复制 source → dest 后,再更新远端 64 bit sig_addr;远端观察到 signal,就表示与这次调用对应的 dest 数据已经交付。这已经把 put + quiet + signal 封装成一项语义明确的操作,但官方文档要求 sig_addr 与 dest 不得重叠,因此它不是数据块内嵌 flag。NVSHMEM Signaling Operations
NCCL 2.29 起提供的 ncclPutSignal 与 ncclWaitSignal 也属于这一类:同一 peer、同一 context 的数据交付和 signal 更新按程序顺序执行,但 signal 仍由 sigIdx 标识。NCCL One-sided Communication
它们的价值是把顺序责任交给库,而不是让应用自己插 fence。相较 Data-as-Flag,它更通用、payload 无需重排,但仍维护独立 signal 空间和完成顺序。
Write-with-Immediate 是带外通知
RDMA Write-with-Immediate 将一个 32 bit immediate value 与 RDMA write 关联,远端在预先提交 receive job 后从完成结果中取得该值。NVIDIA DOCA 文档明确称 immediate 为 out-of-band 数据,因此它更像“payload 到达时顺便生成一个 CQ 事件”,而不是“轮询 payload 自身”。NVIDIA DOCA RDMA Write with Immediate
这条路径适合需要事件分派、长度、序号或类型信息的协议,但它没有消除接收队列与 completion processing。
CRC 证明没传坏,不证明该消费了
NVIDIA 的 NVLink 确实存在 CRC Data、CRC FLIT、Replay 与 Recovery 等链路可靠性机制。NVIDIA DCGM NVLink Counters 但链路 CRC 回答的是“某个 flit/packet 是否损坏”,不是“应用期待的全部 DataBlock 是否已经到齐、属于哪一轮、现在能否复用 buffer”。
要让 checksum 真正承担完成证明,协议还必须定义:
- 应校验哪些 block,以及期望 block 数量;
- checksum 属于哪个 generation;
- 部分旧数据与部分新数据会不会碰巧得到相同结果;
- checksum 自身何时可见,接收方如何重试;
- 失败时是重传、超时还是终止。
因此,链路 CRC 可以是 Data Checksum 的底座,却不能直接替代 Data Checksum 的应用层批次协议。NCCL 2.24 只在 LL/LL128 的内嵌 flag 路径上把 receive completion 标为 optional,也侧面印证了这层区别。
一套统一设计框架
把不同名字拿掉后,这类方案都在设计一个函数:
1 | ready(generation, slot) -> true / false |
一个可用的 ready 必须同时回答“完整性”和“新鲜度”。可以沿六个问题检查设计是否成立:
- 原子单元多大? 如果接收方看到 proof 时,payload 仍可能被撕裂或乱序到达,方案就不正确。TFF 依赖 512 B,LL 依赖 8 B 对,LLA2A 依赖 16 B 槽位。
- 完成粒度多细? 每个 block 一个 proof 能立即流水;每批一个 checksum 节省元数据,却要等待整批。
- 如何区分代际? epoch、sequence number 或双 buffer 用来防止旧数据被误认,这本质上是循环队列里的 ABA 问题。
- 碰撞意味着什么? checksum 碰撞可能产生 false-ready;sentinel 与合法 payload 相等通常产生 false-not-ready,最终表现为死锁。
- buffer 何时复用? 远端 ready 不等于源端可复用,也不等于接收槽位已被消费;仍需要 credit、head/tail 或本地 completion。
- 异常如何退出? 轮询循环必须保留 abort、timeout 与错误传播,否则“极低延迟”会以不可诊断的永久自旋为代价。
这六项可以进一步压缩成三种基本交换:
| 方案 | 用什么换掉独立 flag 路径 | 主要收益 | 主要风险 |
|---|---|---|---|
| 内嵌 flag / epoch | 线上 payload 比例 | 最细粒度、确定性强 | 带宽损失,绑定原子粒度 |
| checksum | 计算与批量等待 | 元数据比例低 | 碰撞、批次延迟、校验开销 |
| sentinel | 接收区初始化与数据域约束 | 接近 100% payload | 碰撞死锁、重置、ABA |
| 独立 signal | 额外控制状态与顺序操作 | 语义通用、易恢复 | 固定通知延迟和状态管理 |
如何选择
Data-as-Flag 并非越“没有 flag”越先进。实际选择更适合遵循下面的顺序:
- 先找硬件保证。 明确 remote store 的原子粒度、写入顺序、cache 可见性和 acquire/release 语义;没有这些保证时,退回显式 fence/signal 才是正确实现。
- 再看消息粒度。 小消息、token 级流水和固定延迟占主导时,TFF、LL、LLA2A 即使牺牲带宽也可能更快;大消息受链路带宽限制时,额外 flag 字节的代价更明显。
- 最后决定容错预算。 不能接受概率性错误的基础通信库,应优先使用 epoch 或序号;只有 payload 域可严格约束、sentinel 可形式化排除时,SP 才能从概率技巧变成协议保证。
- 保留回退路径。 NVIDIA 会按拓扑与平台能力选择 LL、LL128 或 Simple,并警告不要在不支持的平台强开 LL128。可移植实现也应在原子性不满足时回退到 checksum 或显式通知。
总结
回到最初的问题,NVIDIA 的答案可以概括成三层:
- 严格同类:NCCL LL、LL128 和 LLA2A 都把完成 proof 放进数据传输单元,接收 GPU 直接轮询数据 buffer;其中 LLA2A 的
8 B payload + 双 epoch与 UBEP TFF 最接近。 - 语义近亲:NVSHMEM/NCCL PutSignal 与 RDMA Write-with-Immediate 把数据和通知绑定为一次 API 或事务,但仍保留独立 signal、immediate 或 completion。
- 没有公开对应证据:NVIDIA 的链路 CRC 不是 UBEP DC;也没有找到 NCCL/NVSHMEM 采用 payload sentinel 作为完成条件的公开实现。
真正可复用的设计思路不是某一种编码,而是:让“数据可见”与“完成证明可见”共享同一个最小硬件保证,再用 generation 管理复用,用 timeout 管理失败。数据不能凭空变成 flag;它必须携带一个接收方能够无歧义验证的证明。
参考资料
- Yipeng Liu et al., UBEP: Re-architecting Expert Parallelism Communication Library for Production Superpods, ACM SIGCOMM 2026.
- NVIDIA, Networking Reliability and Observability at Scale with NCCL 2.24.
- NVIDIA NCCL 2.31.2,
ncclLLFifoLine,ProtoLL,ProtoLL128与ncclLLA2ASession. - NVIDIA, NVSHMEM Signaling Operations.
- NVIDIA, NCCL One-sided Communication.
- NVIDIA, DOCA RDMA Programming Guide.
- NVIDIA, DCGM NVLink Counters.